#include "Model/Requirement.hpp"
#include "Model/DuplicateNameError.hpp"
#include "Model/InvalidHierarchyOperationError.hpp"
#include "Model/InvalidNameError.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/foreach.hpp>
#include <memory>
#include <cassert>

namespace Model
{

// ---------------------------------------------------------------------------------------------------------------------

Requirement::Requirement( const NameString& name, const RichText& statement)
	: statement_( statement)
	, position_( 0)
	, pParentRequirement_( NULL)
	, name_( name)
	, pParentPackage_( NULL)
{
	if( name_.empty())
	{
		throw InvalidNameError( name_);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

Requirement::~Requirement()
{
	BOOST_FOREACH( Requirement* pCurReq, childRequirements_)
	{
		delete pCurReq;
	}
}

// ---------------------------------------------------------------------------------------------------------------------

String Requirement::id() const
{
	if( NULL == pParentRequirement_)
	{
		// This is a root requirement. No id returned.
		return String();
	}
	String positionStr = boost::lexical_cast< String>( position_);

	return (NULL == pParentRequirement_->pParentRequirement_) ?  positionStr : pParentRequirement_->id() + "." + positionStr;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::setStatement( const RichText& statement )
{
	if( statement == this->statement())
		return;
	statement_ = statement;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::setPosition( std::size_t position )
{
	std::size_t oldPosition = this->position();
	position_ = position;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::setName( const NameString& newName )
{
	// Do nothing if the name is unchanged.
	if( newName == this->name())
	{
		return;
	}
	if( newName.empty())
	{
		throw InvalidNameError( newName);
	}

	// Check for duplicates.
	if( NULL != pParentRequirement_)
	{
		Requirement* pSameNameReq = pParentRequirement_->findByName( newName);
		if( NULL != pSameNameReq && this != pSameNameReq)
		{
			throw DuplicateNameError( newName);
		}
	}
	name_ = newName;
}


// ---------------------------------------------------------------------------------------------------------------------

bool Requirement::hasChildWithName( const std::string& name ) const
{
	using namespace boost::lambda;

	return childRequirements_.end() != std::find_if( 
		childRequirements_.begin(), childRequirements_.end(), 
		bind( &Requirement::name, _1) == name);
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::incrementPosition()
{
	// Make sure this method is called for correct position number (don't allow overflows).
	assert( NULL != pParentRequirement_ && position_ < pParentRequirement_->childRequirements_.size());
	++position_;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::decrementPosition()
{
	// Make sure this method is called for correct position number (don't allow underflows).
	assert( NULL != pParentRequirement_ && position_ > 0);
	--position_;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::insertChild( Requirement* pNewRequirement, ChildIterator iter )
{
	assert( NULL != pNewRequirement);

	iter = childRequirements_.insert( iter, pNewRequirement);
	pNewRequirement->pParentRequirement_ = this;
	pNewRequirement->pParentPackage_ = this->pParentPackage_;
	++iter;

	// Now, adjust the requirement position for the rest of the requirements in the list
	while( iter != childRequirements_.end())
	{
		(*iter)->setPosition( (*iter)->position() + 1);
		++iter;
	}
}


// ---------------------------------------------------------------------------------------------------------------------

Requirement::ChildIterator Requirement::findChildInsertionPosition( std::size_t position )
{
	assert( position <= childRequirements_.size() + 1);

	std::size_t curPos = position;

	// Find the right position to insert the new child
	Requirement::ChildIterator iter = childRequirements_.begin();
	while( curPos > 1)
	{
		--curPos;
		++iter;
	}

	return iter;
}


// ---------------------------------------------------------------------------------------------------------------------

Requirement* Requirement::addChildRequirement( 
	const NameString& name, std::size_t position /*=std::numeric_limits< std::size_t>::max()*/, 
	const RichText& statement /*= RichText()*/ )
{
	position = std::min( position, childRequirements_.size() + 1);

	Requirement::ChildIterator iter = this->findChildInsertionPosition( position);

	std::auto_ptr< Requirement> pNewRequirement( new Requirement( name, statement));

	this->insertChild( pNewRequirement.get(), iter);
	pNewRequirement->setPosition( position);
	return pNewRequirement.release();
}


// ---------------------------------------------------------------------------------------------------------------------

Requirement* Requirement::addChildRequirement( 
	Requirement* pRequirement, std::size_t position /*= std::numeric_limits< std::size_t>::max()*/ )
{
	if( this == &pRequirement->parent())
	{
		this->moveChild( pRequirement, position);
	}
	else
	{
		this->addChildRequirementFromAnotherParent( pRequirement, position);
	}
	return pRequirement;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::removeChildRequirementWithNoChecks( Requirement* pRequirement )
{
	ChildIterator iterToRemove = std::find( childRequirements_.begin(), childRequirements_.end(), pRequirement);
	ChildIterator endIter = childRequirements_.end();
	assert( endIter != iterToRemove);
	ChildIterator next = iterToRemove;
	++next;

	childRequirements_.erase( iterToRemove);

	while( next != endIter)
	{
		(*next)->setPosition( (*next)->position() - 1);
		++next;
	}

	pRequirement->pParentRequirement_ = NULL;
}


// ---------------------------------------------------------------------------------------------------------------------

Requirement* Requirement::removeChildRequirement( Requirement* pRequirement )
{
	this->removeChildRequirementWithNoChecks( pRequirement);
	return pRequirement;
}


// ---------------------------------------------------------------------------------------------------------------------

bool Requirement::isAncestorOf( const Requirement& requirement ) const
{
	Requirement* pParent = requirement.pParentRequirement_;
	while( NULL != pParent && pParent != this)
	{
		pParent = pParent->pParentRequirement_;
	}
	return this == pParent;
}


// ---------------------------------------------------------------------------------------------------------------------

Requirement* Requirement::findByName( const NameString& name ) const
{
	for( ChildConstIterator iter = childRequirements_.begin(); iter != childRequirements_.end(); ++iter)
	{
		if( (*iter)->name() == name)
		{
			return *iter;
		}
	}
	return NULL;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::addChildRequirementFromAnotherParent( Requirement* pRequirement, std::size_t position )
{
	if( pRequirement->isAncestorOf( *this))
	{
		throw InvalidHierarchyOperationError();
	}

	Requirement* pSameNameReq = this->findByName( pRequirement->name());
	if( pSameNameReq != NULL && pRequirement != pSameNameReq)
	{
		// The only case when the duplicated name is allowed, is when the requirement is moved 
		//  inside the same parent.
		throw DuplicateNameError( pRequirement->name());
	}

	Requirement* pOldParent = pRequirement->pParentRequirement_;
	std::size_t oldPosition = pRequirement->position();
	if( NULL != pOldParent)
	{
		pOldParent->removeChildRequirementWithNoChecks( pRequirement);
	}

	position = std::min( position, childRequirements_.size() + 1);

	Requirement::ChildIterator iter = this->findChildInsertionPosition( position);

	this->insertChild( pRequirement, iter);
	pRequirement->position_ = position;
}


// ---------------------------------------------------------------------------------------------------------------------
void Requirement::moveChild( Requirement* pRequirement, std::size_t position )
{
	assert( this == &pRequirement->parent());

	position = std::min( position, childRequirements_.size() + 1);
	std::size_t oldPosition = pRequirement->position();

	ChildIterator firstIter, lastIter;
	ChildIterator oldIter = std::find( childRequirements_.begin(), childRequirements_.end(), pRequirement);

	int positionStep = 0;
	if( position < oldPosition)
	{
		// Move backward
		lastIter = oldIter;
		++lastIter;
		childRequirements_.erase( oldIter);
		firstIter = this->findChildInsertionPosition( position);
		childRequirements_.insert( firstIter, pRequirement);
		positionStep = 1;
	}
	else if( position > oldPosition)
	{
		// Move forward
		firstIter = oldIter;
		firstIter++;
		lastIter = this->findChildInsertionPosition( position);
		childRequirements_.erase( oldIter);
		childRequirements_.insert( lastIter, pRequirement);
		positionStep = -1;
	}
	else
	{
		// No movement actually
		return;
	}

	for( ; firstIter != lastIter; firstIter++)
	{
		(*firstIter)->setPosition( (*firstIter)->position() + positionStep);
	}
	if( lastIter != childRequirements_.end())
	{
		(*lastIter)->setPosition( (*lastIter)->position() + positionStep);
	}
	pRequirement->position_ = position;
}


// ---------------------------------------------------------------------------------------------------------------------

void Requirement::insertComment( const Comment& comment, std::size_t position /*= std::numeric_limits< std::size_t>::max()*/ )
{
	position = std::min( comments_.size() + 1, position);
	CommentIterator iter = this->findCommentIterator( position);
	CommentIterator newCommentIter = comments_.insert( iter, comment);
}


// ---------------------------------------------------------------------------------------------------------------------
Requirement::CommentIterator Requirement::findCommentIterator( std::size_t position )
{
	assert( position <= comments_.size() + 1);

	CommentIterator i = comments_.begin();
	if( position > 1)
		std::advance( i, position - 1);
	return i;
}

// ---------------------------------------------------------------------------------------------------------------------

Requirement::CommentConstIterator Requirement::findCommentIterator( std::size_t position) const
{
	assert( position <= comments_.size() + 1);

	CommentConstIterator i = comments_.begin();
	if( position > 1)
		std::advance( i, position - 1);
	return i;
}

// ---------------------------------------------------------------------------------------------------------------------
Requirement* Requirement::findByPosition( std::size_t position ) const
{
	assert( position <= childRequirements_.size());
	ChildConstIterator i = childRequirements_.begin();
	if( position > 1)
		std::advance( i, position - 1);
	assert( (*i)->position() == position);

	return *i;
}

// ---------------------------------------------------------------------------------------------------------------------

void Requirement::removeComment( std::size_t position)
{
	assert( position <= comments_.size());
	CommentIterator iter = findCommentIterator( position);
	comments_.erase( iter);
}

// ---------------------------------------------------------------------------------------------------------------------

void Requirement::changeComment( const Comment& comment, std::size_t position )
{
	assert( position <= comments_.size());
	CommentIterator iter = findCommentIterator( position);
	*iter = comment;
}

// ---------------------------------------------------------------------------------------------------------------------

Comment& Requirement::commentAt( std::size_t position )
{
	assert( position <= comments_.size());
	return *this->findCommentIterator( position);
}

// ---------------------------------------------------------------------------------------------------------------------

const Comment& Requirement::commentAt( std::size_t position ) const
{
	assert( position <= comments_.size());
	return *this->findCommentIterator( position);
}

// ---------------------------------------------------------------------------------------------------------------------

void Requirement::moveToPackage( Model::Package* pNewPackage )
{
	assert( !this->hasParent());
	this->setPackage( pNewPackage);
}

// ---------------------------------------------------------------------------------------------------------------------

void Requirement::setPackage( Model::Package* pPackage )
{
	pParentPackage_ = pPackage;
	BOOST_FOREACH( Requirement* pCurRequirement, childRequirements_)
	{
		pCurRequirement->setPackage( pPackage);
	}
}

} // namespace Model
